#include "LightSample.h"
#include "Application.h"
#include "utils.h"
#include "Utility.h"
#include "Texture.h"
#include "DiffuseLight.h"
#include "Metal.h"
#include <thread>
namespace RayTracer
{
	void LightSample::Init()
	{
		mImageParam = ImageParam(800, 500, "Light.png");
		lookfrom = vec3(0.0, 0.2, 1.5);
		lookat = vec3(0.0, 0.0, 0.0);
		fov = 60;
		upColor = vec3(0.5, 0.7, 1.0);
		downColor = vec3(1.0);
		groundColor = vec3(0.0);
		bgColor = vec3(0.0);
		leftLightScale = 10;
		rightLightScale = 10;
		upLightScale = 10;
		leftColor = vec3(1.0, 0.0, 0.0)*leftLightScale;
		rightColor = vec3(0.0, 1.0, 0.0)*rightLightScale;
		upLightColor = vec3(1.0)*upLightScale;

		
	}

	void LightSample::Render()
	{
		auto noiseTexture = std::make_shared<NoiseTexture>(4);
		floorMat = std::make_shared<Lambertian>(noiseTexture);
		leftLightMat = std::make_shared<DiffuseLight>(leftColor);
		rightLightMat = std::make_shared<DiffuseLight>(rightColor);
		upLightMat = std::make_shared<DiffuseLight>(upLightColor);

		floor = std::make_shared<XZ_Rect>(-5, 5, -5, 5, -0, floorMat);
		leftLight = std::make_shared<XY_Rect>(-0.8, -0.5, 0.0, 0.3, 0.0, leftLightMat);
		rightLight = std::make_shared<XY_Rect>(0.5, 0.8, 0.0, 0.3, 0.0, rightLightMat);
		upLight = std::make_shared<XZ_Rect>(-0.2, 0.2, -0.2, 0.2, 0.7, upLightMat);
		world.add(floor);
		world.add(leftLight);
		world.add(rightLight);
		world.add(upLight);
		int width = mImageParam.mWidth;
		int height = mImageParam.mHeight;
		camera = Camera(lookfrom, lookat, fov);

		uint8_t* pixels = new uint8_t[width * height * 3];
		int index = 0;
		for (int j = height - 1; j >= 0; j--) {
			for (int i = 0; i < width; ++i) {
				vec3 color = vec3(0.0f);
				for (int s = 0; s < samples_per_pixel; s++)
				{
					auto u = (i + Random01()) / (width - 1);
					auto v = (j + Random01()) / (height - 1);
					Ray r = camera.GetRay(u, v);
					color += RayColor(r,bgColor ,world, maxDepth);
				}
				auto scale = 1.0 / samples_per_pixel;
				color *= scale;

				GammaCorrect(color);
				pixels[index++] = static_cast<uint8_t>(256 * clamp(color.r, 0.0, 0.999));
				pixels[index++] = static_cast<uint8_t>(256 * clamp(color.g, 0.0, 0.999));
				pixels[index++] = static_cast<uint8_t>(256 * clamp(color.b, 0.0, 0.999));
				curIndex++;
			}
		}
		Utils::SaveImage(("Images/" + mImageParam.mName).c_str(), width, height, 3, pixels);
		isGenerate = true;
	}

	void LightSample::ImGui()
	{
		ImGui::Text(u8"Light");
		ImGui::Checkbox(u8"Normal", &isNormal);
		if (!isNormal)
		{
			ImGui::SliderInt(u8"Spp", &samples_per_pixel, 1, 500);
		}
		else
		{
			samples_per_pixel = 1;
		}
		ImGui::SliderInt(u8"MaxDepth", &maxDepth, 1, 50);

		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Camera"))
		{
			ImGui::InputFloat3(u8"Camera Position", &lookfrom[0]);
			ImGui::SliderFloat("Fov", &fov, 20.0, 180.0);
		}

		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Left Light"))
		{
			ImGui::ColorEdit3("Left Light Color",&leftColor[0]);
			ImGui::DragFloat("Left Light Intensity",&leftLightScale,1.0,1.0,50.0);
		}

		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Right Light"))
		{
			ImGui::ColorEdit3("Right Light Color", &rightColor[0]);
			ImGui::DragFloat("Right Light Intensity", &rightLightScale,1.0,1.0,50.0);

		}

		ImGui::Separator();
		if (ImGui::CollapsingHeader(u8"Up Light"))
		{
			ImGui::ColorEdit3("Up Light Color", &upLightColor[0]);
			ImGui::DragFloat("Up Light Intensity", &upLightScale,1.0,1.0,50.0);

		}
		ImGui::Separator(); 
		auto w = (float)mImageParam.mWidth;
		auto h = (float)mImageParam.mHeight;
		auto progress = ((float)curIndex / (w * h));
		ImGui::ProgressBar(progress);
		if (ImGui::Button(u8"Generate")) {
			std::thread t = std::thread([=]() { Render(); });
			t.detach();
		}
		if (isGenerate) {
			auto mTexHandle = Utils::LoadTexture(("Images/" + mImageParam.mName).c_str());
			Application::GetInstance()->SetTexHandle(mTexHandle);
			isGenerate = false;
			curIndex = 0;
		}

	}

	void LightSample::ShutDown()
	{
		Application::GetInstance()->SetTexHandle(0);
	}

	vec3 LightSample::RayColor(const Ray& r, const vec3& background, const Hitable& world, int depth)
	{
		HitRecord rec;

		if (depth <= 0)
			return vec3(0, 0, 0);

		if (!world.Hit(r, 0.001, infinity, rec))
			return background;

		Ray scattered;
		vec3 attenuation;
		vec3 emitted = rec.matPtr->Emitted(rec.u, rec.v, rec.position);

		if (!rec.matPtr->Scatter(r, rec, attenuation, scattered))
			return emitted;

		return emitted + attenuation * RayColor(scattered, background, world, depth - 1);
	}
}